package cn.seaboot.admin.quartz.core;

import cn.seaboot.admin.quartz.bean.ScheduleJob;
import cn.seaboot.commons.exception.BizException;
import cn.seaboot.commons.exception.ServiceException;
import cn.seaboot.commons.exception.SystemError;
import cn.seaboot.commons.lang.P;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
import org.quartz.utils.Key;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

/**
 * quartz 二次封装
 * <p>
 * 就是简单的 quartz 二次封装，起到规范代码的作用，告诉其他人，我会这么使用 quartz。
 *
 * @author Mr.css
 * @version 2019/12/7 10:19
 */
@Configuration
public class QuartzManager implements InitializingBean, Consumer<QuartzJobBuilder> {
    private final Logger logger = LoggerFactory.getLogger(QuartzManager.class);

    /**
     * 调度器
     */
    private Scheduler scheduler;

    public Scheduler getScheduler() {
        return scheduler;
    }

    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    /**
     * 初始化, 默认使用 StdSchedulerFactory.getDefaultScheduler()
     *
     * @throws SystemError 如果计划任务启动失败，终止整个项目的执行
     */
    public void start() {
        try {
            if (scheduler == null) {
                scheduler = StdSchedulerFactory.getDefaultScheduler();
            }
            if (!scheduler.isStarted()) {
                scheduler.start();
            } else {
                logger.warn("[--- Configuration ---] scheduler repeat startup，had better have a check!");
            }
        } catch (SchedulerException e) {
            // fatal exception, stop application
            throw new SystemError("[--- Configuration ---] scheduler started failed！", e);
        }
    }

    /**
     * 功能：关闭所有定时任务
     *
     * @throws SchedulerException -
     */
    public void shutdown() throws SchedulerException {
        if (scheduler != null && !scheduler.isShutdown()) {
            scheduler.shutdown();
        }
    }

    /**
     * 使用 QuartzJobBuilder 创建一个任务
     *
     * @param quartz the input argument
     */
    @Override
    public void accept(QuartzJobBuilder quartz) {
        // job
        JobBuilder builder = JobBuilder
                .newJob(quartz.getJobClass())
                .withIdentity(quartz.getJobName(), quartz.getJobGroup());
        if (quartz.getJobDataMap() != null) {
            builder.setJobData(quartz.getJobDataMap());
        }
        JobDetail jobDetail = builder.build();

        // corn
        CronScheduleBuilder sb = CronScheduleBuilder
                .cronSchedule(quartz.getCron())
                .inTimeZone(P.timeZone());

        // trigger
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity(quartz.getTriggerName(), quartz.getTriggerGroup())
                .startNow()
                .withSchedule(sb)
                .build();

        try {
            // execute
            scheduler.scheduleJob(jobDetail, trigger);
        } catch (SchedulerException e) {
            throw new ServiceException(e);
        }
    }

    /**
     * 返回一个任务构建器，用于创建任务
     *
     * @return -
     */
    public QuartzJobBuilder builder() {
        return new QuartzJobBuilder(this);
    }

    /**
     * 生成一个触发器
     *
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名称
     * @param cron         表达式
     * @return 触发器
     */
    public CronTrigger genCronTrigger(
            String triggerGroup, String triggerName, String cron) {
        TriggerBuilder<Trigger> triggerBuilder = TriggerBuilder.newTrigger();
        triggerBuilder.withIdentity(triggerName, triggerGroup);
        triggerBuilder.startNow();
        triggerBuilder.withSchedule(CronScheduleBuilder.cronSchedule(cron).inTimeZone(P.timeZone()));
        return (CronTrigger) triggerBuilder.build();
    }

    /**
     * 触发定时任务
     *
     * @param jobName  任务名
     * @param jobGroup 分组名称
     * @throws SchedulerException On error
     */
    public void triggerJob(String jobGroup, String jobName) throws SchedulerException {
        scheduler.triggerJob(JobKey.jobKey(jobName, jobGroup));
    }

    /**
     * 触发定时任务，会暂停触发器上的 Job
     *
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名
     * @throws SchedulerException On error
     */
    public void pauseTrigger(String triggerGroup, String triggerName) throws SchedulerException {
        scheduler.pauseTrigger(TriggerKey.triggerKey(triggerName, triggerGroup));
    }

    /**
     * 移除一个触发器
     * <p>
     * 移除触发器不会影响关联的 Job，Job 仍然存在于调度器中。
     * <p>
     * 如果需要完全移除 Job，可以使用 deleteJob(JobKey jobKey) 方法。
     *
     * @param triggerGroup 触发器分组
     * @param triggerName  触发器名
     * @return if false 说明不存在对应的 trigger
     * @throws SchedulerException -
     */
    public boolean removeTrigger(String triggerGroup, String triggerName) throws SchedulerException {
        return scheduler.unscheduleJob(TriggerKey.triggerKey(triggerName, triggerGroup));
    }

    /**
     * 触发定时任务，会暂停与之关联的所有触发器
     *
     * @param jobName  任务名
     * @param jobGroup 分组名称
     * @throws SchedulerException On error
     */
    public void pauseJob(String jobGroup, String jobName) throws SchedulerException {
        scheduler.pauseJob(JobKey.jobKey(jobName, jobGroup));
    }


    /**
     * 触发定时任务，会暂停与之关联的所有触发器
     *
     * @param jobGroup 分组名称
     * @throws SchedulerException On error
     */
    public void pauseJobGroup(String jobGroup) throws SchedulerException {
        scheduler.pauseJobs(GroupMatcher.jobGroupEquals(jobGroup));
    }


    /**
     * 移除一个任务，包含触发器
     * <p>
     * job 与 trigger 是强关联关系，一个 job 对应于多个 trigger，
     * 删除 job 的时候，相关的 trigger 都会删除。
     *
     * @param jobGroup 分组
     * @param jobName  任务名
     * @throws SchedulerException -
     */
    public void deleteJob(String jobGroup, String jobName) throws SchedulerException {
        scheduler.deleteJob(JobKey.jobKey(jobName, jobGroup));
    }

    /**
     * 修改一个任务的触发时间
     * <p>
     * 方法通用
     *
     * @param triggerGroup 触发器组名
     * @param triggerName  触发器名
     * @param cron         时间设置，参考quartz说明文档
     */
    public void modifyJob(String triggerGroup, String triggerName, String cron) throws SchedulerException {
        TriggerKey triggerKey = TriggerKey.triggerKey(triggerName, triggerGroup);
        CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
        if (trigger == null) {
            throw new IllegalArgumentException("trigger not found: " + triggerGroup + " _ " + triggerName);
        } else {
            if (!trigger.getCronExpression().equals(cron)) {
                trigger = this.genCronTrigger(triggerGroup, triggerName, cron);
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        }
    }

    /**
     * 获取定时任务未来 n 次的触发时间
     *
     * @param cron 表达式
     * @param n    次数
     * @return 时间
     */
    public List<Date> getNextFireTime(String cron, int n) {
        try {
            CronExpression cronExpression = new CronExpression(cron);
            List<Date> dates = new ArrayList<>();
            Date date = new Date();
            int cnt = n;
            while (cnt-- > 0) {
                date = cronExpression.getNextValidTimeAfter(date);
                if (date == null) {
                    break;
                } else {
                    dates.add(date);
                }
            }
            return dates;
        } catch (Exception e) {
            throw new BizException("the cron expression is incorrect: " + cron, e);
        }
    }

    /**
     * 获取当前系统所有的计划任务
     * <p>
     * 不经过数据库，也是可以直接操作的。
     *
     * @throws SchedulerException -
     */
    public List<ScheduleJob> getScheduleJobList() throws SchedulerException {
        GroupMatcher<TriggerKey> gm = GroupMatcher.anyTriggerGroup();
        Set<TriggerKey> set = scheduler.getTriggerKeys(gm);
        List<ScheduleJob> res = new ArrayList<>(set.size());
        for (TriggerKey triggerKey : set) {
            Trigger trigger = scheduler.getTrigger(triggerKey);
            JobKey jobKey = trigger.getJobKey();
            JobDetail detail = scheduler.getJobDetail(jobKey);

            ScheduleJob scheduleJob = new ScheduleJob();
            scheduleJob.setJobGroup(jobKey.getGroup());
            scheduleJob.setJobName(jobKey.getName());
            scheduleJob.setTriggerGroup(triggerKey.getGroup());
            scheduleJob.setTriggerName(triggerKey.getName());
            scheduleJob.setJobClass(detail.getJobClass().getName());
            res.add(scheduleJob);
        }
        return res;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.start();
    }
}
